package zdream.control.test.region;

import org.junit.Before;
import org.junit.Test;

import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

import zdream.control.region.*;

import static org.junit.Assert.*;
import static zdream.control.region.TerrainConstant.*;

/**
 * @author Zdream
 * @date 2023-12-23
 * @since 0.0.1
 */
public class RegionBuilderCase {
	@Before
	public void before() {
	}

	@Test
	public void testSimpleRegionBuilder1() {
		Region region = new SimpleRegionBuilder(this.getClass().getSimpleName())
				.withRoomPositions("13")
				.withRoom(1)
						.terrain("#########################")
						.terrain("#########################")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                       ")
						.terrain("##                       ")
						.terrain("##                       ")
						.terrain("#########################")
						.terrain("#########################")
						.terrain("#########################")
						.build()
				.withRoom(3)
						.terrain("#########################")
						.terrain("#########################")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("                       ##")
						.terrain("                       ##")
						.terrain("                       ##")
						.terrain("#########################")
						.terrain("#########################")
						.terrain("#########################")
						.build()
				.build();
		System.out.println(region);

		assertEquals(2, region.rooms.length);
		assertEquals(1, region.rooms[0].id);
		assertEquals(Region.BLOCK_WIDTH, region.rooms[0].width);
		assertEquals(Region.BLOCK_HEIGHT, region.rooms[0].height);
		assertEquals(0, region.rooms[0].offsetX);
		assertEquals(0, region.rooms[0].offsetY);

		byte[][] terrains = region.rooms[0].terrains;
		for (int x = 0; x < terrains.length; x++) {
			byte[] column = terrains[x];
			for (int y = 0; y < column.length; y++) {
				if (y < 3 || y > 11 || x < 2) {
					assertEquals(x + "," + y, TerrainConstant.TERRAIN_SOLID, column[y]);
				} else if (x > 22 && y > 5) {
					assertEquals(x + "," + y, TerrainConstant.TERRAIN_SOLID, column[y]);
				} else {
					assertEquals(x + "," + y, TerrainConstant.TERRAIN_EMPTY, column[y]);
				}
			}
		}

		assertEquals(1, region.rooms[0].gates.length);
		assertEquals(Gate.DIRECTION_RIGHT, region.rooms[0].gates[0].direction);
		assertArrayEquals(new int[] {3, 4, 5}, region.rooms[0].gates[0].exits); // y = [3, 4, 5]

		assertEquals(3, region.rooms[1].id);
		assertEquals(Region.BLOCK_WIDTH, region.rooms[1].width);
		assertEquals(Region.BLOCK_HEIGHT, region.rooms[1].height);
		assertEquals(Region.BLOCK_WIDTH, region.rooms[1].offsetX);
		assertEquals(0, region.rooms[1].offsetY);

		terrains = region.rooms[1].terrains;
		for (int x = 0; x < terrains.length; x++) {
			byte[] column = terrains[x];
			for (int y = 0; y < column.length; y++) {
				if (y < 3 || y > 11 || x > 22) {
					assertEquals(x + "," + y, TerrainConstant.TERRAIN_SOLID, column[y]);
				} else if (x < 2 && y > 5) {
					assertEquals(x + "," + y, TerrainConstant.TERRAIN_SOLID, column[y]);
				} else {
					assertEquals(x + "," + y, TerrainConstant.TERRAIN_EMPTY, column[y]);
				}
			}
		}

		assertEquals(1, region.rooms[1].gates.length);
		assertEquals(Gate.DIRECTION_LEFT, region.rooms[1].gates[0].direction);
		assertArrayEquals(new int[] {3, 4, 5}, region.rooms[1].gates[0].exits); // y = [3, 4, 5]
	}

	@Test
	public void testSimpleRegionBuilder2() {
		Region region = new SimpleRegionBuilder(this.getClass().getSimpleName())
				.withRoomPositions("1")
				.withRoom(1)
						.terrain("#########################")
						.terrain("#########################")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("#########################")
						.terrain("#########################")
						.terrain("#########################")
						.build()
				.defineFoe("foe1", "testFoe")
				.withFoeSpawnAt(6, 3, "foe1", "point1")
				.withFoeSpawnAt(9, 3, "foe1", "point2")
				.withFoeSpawnAt(11, 3, "foe1", "point3")
				.build();
		System.out.println(region);

		assertEquals(1, region.rooms.length);
		assertEquals(1, region.rooms[0].id);
		assertEquals(Region.BLOCK_WIDTH, region.rooms[0].width);
		assertEquals(Region.BLOCK_HEIGHT, region.rooms[0].height);
		assertEquals(0, region.rooms[0].offsetX);
		assertEquals(0, region.rooms[0].offsetY);

		assertEquals(3, region.points.length);
		for (InterestPoint point : region.points) {
			assertTrue(point.isForFoeSpawn());

			final FoeSpawnPoint spawnPoint = point.asFoeSpawn();
			assertEquals("testFoe", spawnPoint.spec.name);
			assertEquals(0, spawnPoint.spec.x, 0); // is not set
			assertEquals(0, spawnPoint.spec.y, 0); // is not set
			assertFalse(spawnPoint.spec.xSet);
			assertFalse(spawnPoint.spec.ySet);

			if (Objects.equals(spawnPoint.name, "point1")) {
				assertEquals(0, 6, spawnPoint.xInRoom);
				assertEquals(0, 6, spawnPoint.xInRegion);
			} else if (Objects.equals(spawnPoint.name, "point2")) {
				assertEquals(0, 9, spawnPoint.xInRoom);
				assertEquals(0, 9, spawnPoint.xInRegion);
			} else if (Objects.equals(spawnPoint.name, "point3")) {
				assertEquals(0, 11, spawnPoint.xInRoom);
				assertEquals(0, 11, spawnPoint.xInRegion);
			} else {
				throw new RuntimeException("can not be here");
			}
		}
	}

	@Test
	public void testSimpleRegionBuilder3() {
		Region region = new SimpleRegionBuilder(this.getClass().getSimpleName())
				.withRoomPositions("1")
				.withRoom(1)
						.defineTerrain('A', TERRAIN_EMPTY)
						.terrain("#########################")
						.terrain("#########################")
						.terrain("##          S          ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##                     ##")
						.terrain("##          A          ##")
						.terrain("##         A A         ##")
						.terrain("##        A   A        ##")
						.terrain("##  S    A     A    T  ##")
						.terrain("#########################")
						.terrain("#########################")
						.terrain("#########################")
						.build()
				.defineFoe("foe1", "testFoe")
				.withFoeSpawnAt('A', "foe1", 0.2f, 0)
				.withSavePoint('S')
				.withStartPoint('T')
				.build();
		System.out.println(region);

		assertEquals(1, region.rooms.length);
		assertEquals(1, region.rooms[0].id);
		assertEquals(Region.BLOCK_WIDTH, region.rooms[0].width);
		assertEquals(Region.BLOCK_HEIGHT, region.rooms[0].height);
		assertEquals(0, region.rooms[0].offsetX);
		assertEquals(0, region.rooms[0].offsetY);

		assertEquals(10, region.points.length);
		assertEquals(7, Arrays.stream(region.points).filter(InterestPoint::isForFoeSpawn).count());
		assertEquals(3, Arrays.stream(region.points).filter(InterestPoint::isForSave).count());

		for (InterestPoint point : region.points) {
			if (!point.isForFoeSpawn()) {
				continue;
			}
			final FoeSpawnPoint spawnPoint = point.asFoeSpawn();
			assertEquals("testFoe", spawnPoint.spec.name);
			assertEquals(0, spawnPoint.spec.x, 0); // is not set
			assertEquals(0, spawnPoint.spec.y, 0); // is not set
			assertFalse(spawnPoint.spec.xSet);
			assertFalse(spawnPoint.spec.ySet);
			assertEquals(point.xInRegion, point.xInRoom, 0.001);
			assertEquals(point.yInRegion, point.yInRoom, 0.001);
		}

		List<InterestPoint> foeSpawnPoints = Arrays.stream(region.points)
				.filter(InterestPoint::isForFoeSpawn)
				.collect(Collectors.toList());

		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 9.2f && p.yInRegion == 3));
		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 10.2f && p.yInRegion == 4));
		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 11.2f && p.yInRegion == 5));
		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 12.2f && p.yInRegion == 6));
		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 13.2f && p.yInRegion == 5));
		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 14.2f && p.yInRegion == 4));
		assertTrue(foeSpawnPoints.stream().anyMatch(p -> p.xInRegion == 15.2f && p.yInRegion == 3));

		List<SavePoint> savePoints = Arrays.stream(region.points)
				.filter(InterestPoint::isForSave)
				.map(InterestPoint::asSave)
				.collect(Collectors.toList());

		assertTrue(savePoints.stream().anyMatch(p -> p.xInRegion == 4.5f && p.yInRegion == 3 && !p.startPoint));
		assertTrue(savePoints.stream().anyMatch(p -> p.xInRegion == 20.5f && p.yInRegion == 3 && p.startPoint));
		assertTrue(savePoints.stream().anyMatch(p -> p.xInRegion == 12.5f && p.yInRegion == 11 && !p.startPoint));
	}
}
